/******************************************************************************\
    SubmitQueue.cpp contains a class of submit queue.
    It's used with acm server. See main.cpp for details.
    Copyright (C) 2008  Victor Vinogradov (fly3333@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
\******************************************************************************/

#include ".\SubmitQueue.h"
#include ".\Common.h"
#include <Common\Memory.hpp>
using namespace std;
using namespace nsContestContestantProblemDataBase;
using namespace nsContestProblemDataBase;
using namespace nsProblemDataBase;
using namespace nsLanguageDataBase;
using namespace nsPluginDataBase;
using namespace nsContestContestantDataBase;
using namespace nsContestantDataBase;

cWardSubmitQueue::cWardSubmitQueue()
	: cBaseThread( false )
{
	if( !Semaphore.Create( NULL, 0, 65535, NULL ) )
	{
		Log.Error( "cSubmitQueue::cSubmitQueue: Semaphore.Create returned false.\n" );
		throw 0;
	}
	if( !TerminateEvent.Create( NULL, FALSE, FALSE, NULL ) )
	{
		Log.Error( "cSubmitQueue::cSubmitQueue: TerminateEvent.Create returned false.\n" );
		throw 0;
	}
	Log.Notice( "cWardSubmitQueue::cWardSubmitQueue: Submit queue has been created.\n" );
}

cWardSubmitQueue::~cWardSubmitQueue()
{
	TerminateEvent.Set();
	WaitForSingleObject( Thread, INFINITE );
	Log.Notice( "cWardSubmitQueue::cWardSubmitQueue: Submit queue has been destroyed.\n" );
}

unsigned long cWardSubmitQueue::Process()
{
	try
	{
		Log.Notice( "cWardSubmitQueue::Process: Submit queue has been started!\n" );
		static const DWORD WAIT_OBJECT_TERMINATE_EVENT = WAIT_OBJECT_0;
		static const DWORD WAIT_OBJECT_SEMAPHORE = WAIT_OBJECT_0 + 1;
		HANDLE handle[] = { TerminateEvent, Semaphore };
		while( true )
		{
			DWORD wait_result = WaitForMultipleObjects( 2, handle, FALSE, INFINITE );
			if( wait_result == WAIT_OBJECT_TERMINATE_EVENT )
			{
				cLocker locker( CriticalSection );
				while( !Queue.empty() )
				{
					unsigned long submit_id = Queue.front();
					Queue.pop();
					sContestContestantProblemData contest_contestant_problem_data;
					if( !ContestContestantProblemDataBase->GetByKey( submit_id, contest_contestant_problem_data ) )
					{
						Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->GetByKey returned false.\n" );
						continue;
					}
					contest_contestant_problem_data.Result = RESULT_TESTING_INTERRUPTED;
					ContestContestantProblemDataBase->Update( contest_contestant_problem_data );
				}
				break;
			}
			else if( wait_result == WAIT_OBJECT_SEMAPHORE )
			{
				Log.Notice( "cSubmitQueue::Process: New submit is accepted by submit queue.\n" );
				unsigned long submit_id;
				{
					cLocker locker( CriticalSection );
					submit_id = Queue.front();
					Queue.pop();
				}
				sContestContestantProblemData contest_contestant_problem_data;
				if( !ContestContestantProblemDataBase->GetByKey( submit_id, contest_contestant_problem_data ) )
				{
					Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->GetByKey returned false.\n" );
					continue;
				}
				contest_contestant_problem_data.Result = RESULT_RUNNING;
				if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
				{
					Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
				contest_contestant_problem_data.Result = RESULT_SUBMISSION_ERROR;
				sContestProblemData contest_problem_data;
				if( !ContestProblemDataBase->GetByKey( contest_contestant_problem_data.ContestProblemId, contest_problem_data ) )
				{
					Log.Warning( "cSubmitQueue::Process: ContestProblemDataBase->GetByKey returned false.\n" );
					if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
						Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
				sProblemData problem_data;
				if( !ProblemDataBase->GetByKey( contest_problem_data.ProblemId, problem_data ) )
				{
					Log.Warning( "cSubmitQueue::Process: ProblemDataBase->GetByKey returned false.\n" );
					if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
						Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
				sLanguageData language_data;
				if( !LanguageDataBase->GetByKey( contest_contestant_problem_data.LanguageId, language_data ) )
				{
					Log.Warning( "cSubmitQueue::Process: LanguageDataBase->GetByKey returned false.\n" );
					if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
						Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
				cReadLocker locker( GetPluginCriticalSection( problem_data.PluginId ) );
				sPluginData plugin_data;
				if( !PluginDataBase->GetByKey( problem_data.PluginId, plugin_data ) )
				{
					Log.Warning( "cSubmitQueue::Process: PluginDataBase->GetByKey returned false.\n" );
					if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
						Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
				if( !( plugin_data.Flags & nsPluginDataBase::FLAG_ENABLED ) || plugin_data.Handle == NULL )
				{
					Log.Warning( "cSubmitQueue::Process: Plugin is not enabled.\n" );
					if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
						Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
				sContestContestantData contest_contestant_data;
				if( !ContestContestantDataBase->GetByKey( contest_contestant_problem_data.ContestContestantId, contest_contestant_data ) )
				{
					Log.Warning( "cSubmitQueue::Process: ContestContestantDataBase->GetByKey returned false.\n" );
					if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
						Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
				sContestantData contestant_data;
				if( !ContestantDataBase->GetByKey( contest_contestant_data.ContestantId, contestant_data ) )
				{
					Log.Warning( "cSubmitQueue::Process: ContestantDataBase->GetByKey returned false.\n" );
					if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
						Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
				string code;
				if( !ContestContestantProblemDataFile.LoadStringAndFlush( contest_contestant_problem_data.Code, contest_contestant_problem_data.CodeSize, code ) )
				{
					Log.Warning( "cSubmitQueue::Process: LoadString returned false.\n" );
					if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
						Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
				sSubmit submit;
				submit.ProblemId = problem_data.Index;
				submit.LanguageId = language_data.Index;
				submit.Code = code.c_str();
				submit.CodeLength = code.length();
				if( plugin_data.MainProc( SUBMIT, 0, &submit ) )
				{
					Log.Notice( "cSubmitQueue::Process: Submit was successfull. %lu:%02lu:%02lu, \"%s\", \"%s\", \"%s\"\n", contest_contestant_problem_data.RelativeTime / 60 / 60, contest_contestant_problem_data.RelativeTime / 60 % 60, contest_contestant_problem_data.RelativeTime % 60, contestant_data.Name, problem_data.Header, language_data.Title );
					contest_contestant_problem_data.Result = submit.Result;
					contest_contestant_problem_data.TestNumber = submit.TestNumber;
					contest_contestant_problem_data.RunTime = submit.Time;
					contest_contestant_problem_data.Memory = submit.Memory;
					unsigned long last_compilation_error_length;
					if( plugin_data.MainProc( GET_LAST_COMPILATION_ERROR_LENGTH, 0, &last_compilation_error_length ) )
					{
						cMemory< char > last_compilation_error( last_compilation_error_length + 1 );
						if( plugin_data.MainProc( GET_LAST_COMPILATION_ERROR, 0, last_compilation_error ) &&
							 ContestContestantProblemDataFile.SaveStringAndFlush( string( last_compilation_error ), contest_contestant_problem_data.CompilationError ) )
						{
							contest_contestant_problem_data.CompilationErrorSize = last_compilation_error_length;
						}
						//else
						//{
						//	contest_contestant_problem_data.CompilationError = 0;
						//	contest_contestant_problem_data.CompilationErrorSize = 0;
						//}
					}
					//else
					//{
					//	contest_contestant_problem_data.CompilationError = 0;
					//	contest_contestant_problem_data.CompilationErrorSize = 0;
					//}
				}
				else
				{
					Log.Warning( "cSubmitQueue::Process: plugin_data.MainProc(SUBMIT) returned false.\n" );
					contest_contestant_problem_data.Result = RESULT_SUBMISSION_ERROR;
					contest_contestant_problem_data.TestNumber = 0;
					contest_contestant_problem_data.RunTime = 0;
					contest_contestant_problem_data.Memory = 0;
					contest_contestant_problem_data.CompilationError = 0;
					contest_contestant_problem_data.CompilationErrorSize = 0;
				}
				if( !ContestContestantProblemDataBase->Update( contest_contestant_problem_data ) )
				{
					Log.Warning( "cSubmitQueue::Process: ContestContestantProblemDataBase->Update returned false.\n" );
					continue;
				}
			}
		}
		Log.Notice( "cSubmitQueue::Process: Submit queue have terminated.\n" );
		return 0;
	}
	catch( ... )
	{
		Log.Error( "cSubmitQueue::Process: Some exception has occured.\n" );
		MessageBox( NULL, "cSubmitQueue::Process: Some exception has occured. See log.txt for details.", "Error", MB_OK | MB_ICONWARNING );
		return 0;
	}
}

bool cWardSubmitQueue::Enqueue( unsigned long submit_id )
{
	cLocker locker( CriticalSection );
	if( !Semaphore.Release( 1, NULL ) )
		return false;
	Queue.push( submit_id );
	return true;
}
